#include <iostream>
#include <fstream>
#include <map>
#include <cstdlib>
#include <mpi.h>
#include "read_words.h"
#include "random_words.h"

#define ROOT 0

using namespace std;

/*
 * Aloca uma matriz na forma um vetor de tamanho rows * cols e retorna um ponteiro para um vetor
 *      de forma que este possa ser acessado como as posicoes de uma matriz.
 */
char **alloc_2d_char(int rows, int cols) {
        int i;

        char *data = (char *) malloc(rows * cols * sizeof(char));
        char **array = (char **) malloc(rows * sizeof(char*));
        for (i = 0; i < rows; i++)
                array[i] = &(data[cols * i]);

        return array;
}

int main(int argc, char *argv[]) {
	unsigned int word_count = 0, gen_small_words = 0, big_words_cnt = 0;
	string word;
	ifstream in;

	map<string, int> small_words; /* Mapeamento das palavras do arquivo com MENOS de 5 caracteres */
	map<string, int> big_words; /* Mapeamento das palavras do arquivo com MAIS de 5 caracteres */

	char **gen_words; /* Vetor para coleta das palavras geradas por todos os nós a cade iteração */
	char **gen_loc_words; /* Vetor para armazenar as palavras geradas a cada iteração */

	int stop = 0; /* Flag que sinaliza parada quando todas as palavras menores que 6 foram encontradas */

	int rank, size;
	double startTime;

	MPI_Init( &argc,&argv); /* Inicia o ambiente paralelo */
        MPI_Comm_rank(MPI_COMM_WORLD, &rank); /* Retorna o rank do processo */
        MPI_Comm_size(MPI_COMM_WORLD, &size); /* Retorna o número de processos no comunicador */

	gen_words = alloc_2d_char(WORDS_PER_NODE * (size + 1), 6);
	gen_loc_words = alloc_2d_char(WORDS_PER_NODE * (size + 1), 6);

	/* Apenas o nó mestre */
	if (rank == 0) {

		cout << "Reading input file... ";

		startTime = MPI_Wtime();

		try {
			/* Carrega as palavras do arquivo na memoria */
			in.open(argv[1], ifstream::in);
			int i = 0, j = 0;
			while (!in.eof()) {
				word = read_word(in); /* Le uma palavra */
				/* Verifica se a palavra eh valida */
				if (validWord(word)) {
					int length = word.length(); /* Caso a palavra seja valida separa ela de acordo com seu tamanho e a insere no set adequado */
					if (length > 1 && length < 6 && small_words.find(word) == small_words.end()) {
						small_words.insert(pair<string, int>(word, i));
						i++;
						word_count++; /* Total de palavras a serem geradas */
					}
					else if (length > 1 && length > 5 && big_words.find(word) == big_words.end()) {
						big_words.insert(pair<string, int>(word, j));
						j++;
						word_count++; /* Total de palavras a serem geradas */
					}
				}
			}

			cout << "Done." << endl;

		}
		catch (ifstream::failure e) {
			cout << "Exception opening/reading file. " << e.what() << endl;
		}

		in.close();

		/* Cria mapas das palavras procuradas com base nas palavras lidas anteriormente */
		map<string, int> small_words_to_find(small_words);
		map<string, int> big_words_to_find(big_words);
		
		cout << "Looking for words... " << endl;

		int percent = 0;

		float ten_percent = 0;
		float count = 0.05;

		while (!stop) {

			/* Gera o vetor local de palavras aleatórias */
			generateWord(gen_loc_words);

			/* Faz coleta dos vetores de palavras gerados em cada nó */
			MPI_Gather(&(gen_loc_words[0][0]), WORDS_PER_NODE * 6, MPI_CHAR, &(gen_words[0][0]), WORDS_PER_NODE * 6, MPI_CHAR, ROOT, MPI_COMM_WORLD);

			/* Percorre o vetor a procura de palavras válidas */
			for (int i = 0; i < WORDS_PER_NODE * (size + 1); i++) {
				string str(gen_words[i]); /* pega uma palavra do vetor */
				
				map<string, int>::iterator it = small_words_to_find.find(str); /* procura por ela no mapa de palavras procuradas menores que 5 */
				if (it != small_words_to_find.end()) { /* se encontrada ela é retirada do mapa e o número de palavras é concatenado */
					if(it->first == str) {
						gen_small_words++;
						small_words_to_find.erase(it);		
					}
				}

				/* Procura por palavras com mais de 5 caracteres a partir do mapa de palavras menores */
				if (big_words_to_find.size() != 0)
					big_words_cnt += searchBigWords(&small_words, &big_words_to_find);

			}

			ten_percent = (float) gen_small_words / small_words.size();

			/* Calcula a porcentagem de palavras menores encontradas e exibe uma mensagem a cada 10% */
			if (count != 0 && ten_percent >= count) {
				count += 0.05;
				//count += ten_percent;
				cout << count * 100 << "% words found [" << gen_small_words << "/" << big_words_cnt << "] (" << MPI_Wtime() - startTime << ")" << endl;
			}

			/* Verifica se todas as palavras foram encontradas */
			if (small_words_to_find.size() == 0 && big_words_to_find.size() == 0)
				stop = 1;

			/* Faz broadcast do flag indicador de parada */
			MPI_Bcast(&stop, 1, MPI_INT, ROOT, MPI_COMM_WORLD);

		}

		cout << "Done." << endl << "EXECUTION TIME: " << MPI_Wtime() - startTime << " seconds" << endl << endl;
	}
	/* Nós escravos */
	else {
		while (!stop) {

			/* Gera o vetor local de palavras aleatórias */
			generateWord(gen_loc_words);

			/* Nó mestre faz a coleta dos vetores de palavras gerados em cada nó */
			MPI_Gather(&(gen_loc_words[0][0]), WORDS_PER_NODE * 6, MPI_CHAR, &(gen_words[0][0]), WORDS_PER_NODE * 6, MPI_CHAR, ROOT, MPI_COMM_WORLD);

			/* Recebe broadcast do flag indicador de parada */
			MPI_Bcast(&stop, 1, MPI_INT, ROOT, MPI_COMM_WORLD);
		}
	}

	MPI_Finalize();

	return 0;
}
